Skip to content

Node SDK: Handle tool and permission broadcasts via event model#686

Draft
SteveSandersonMS wants to merge 11 commits intomainfrom
stevesa/handle-broadcasts-for-permissions-and-tools
Draft

Node SDK: Handle tool and permission broadcasts via event model#686
SteveSandersonMS wants to merge 11 commits intomainfrom
stevesa/handle-broadcasts-for-permissions-and-tools

Conversation

@SteveSandersonMS
Copy link
Contributor

Overview

Replace the RPC callback pattern for external tool calls and permission requests with a broadcast event model. The runtime now broadcasts external_tool.requested and permission.requested as session events to all connected clients, and clients respond via session.tools.handlePendingToolCall and session.permissions.handlePendingPermissionRequest RPC methods.

Changes

Event-based handling (Node SDK):

  • Remove tool.call and permission.request RPC request handlers from client.ts
  • Add _handleBroadcastEvent in session.ts that intercepts broadcast request events, executes local handlers, and responds via the new RPC methods
  • Remove normalizeToolResult, buildUnsupportedToolResult, and related helper methods that are no longer needed

Codegen updates:

  • Regenerated rpc.ts and session-events.ts from updated runtime schemas
  • permission.completed event now includes result.kind enum for observing outcomes

Protocol version:

  • Bump SDK_PROTOCOL_VERSION from 2 to 3 to match the runtime breaking change

E2E tests (5 new multi-client tests):

  • Both clients see tool request and completion events
  • One client approves permission, both see the approved result
  • One client rejects permission, both see the denied result
  • Two clients register different tools (union semantics), agent uses both
  • Disconnecting client removes its tools while other client's tools persist

⚠️ E2E tests will fail until the corresponding runtime update is released

The runtime must include the broadcast model changes and per-connection tool tracking for these tests to pass. Until then, E2E tests that depend on the new protocol (multi-client tests, and any tests using protocol version 3) will fail.

SteveSandersonMS and others added 7 commits March 5, 2026 12:16
…callbacks

Update the Node SDK to listen for broadcast events (external_tool.requested,
permission.requested) and respond via sharedApi RPC methods
(session.tools.handlePendingToolCall, session.permissions.handlePendingPermissionRequest)
instead of the old tool.call/permission.request RPC handlers.

- Regenerate rpc.ts and session-events.ts from runtime schemas
- Add _handleBroadcastEvent to session.ts for event interception
- Remove old RPC handlers from client.ts
- Add useStdio option to test harness for TCP mode

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Verify that when two SDK clients are connected to the same CLI process,
both see external_tool.requested/completed and permission.requested/completed
events. The permission test has client 1 manually approve while client 2
observes, confirming the broadcast model works across connections.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ent 1 approves

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…rtions

Add E2E test verifying one client can reject a permission and both clients
see the denial in the permission.completed event. Regenerate SDK types from
updated runtime schemas so permission.completed includes result.kind enum.
Use type-narrowing filters instead of casts in test assertions.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…leanup

- two clients register different tools and agent uses both: client1 has
  city_lookup, client2 has currency_lookup, verifies union semantics
- disconnecting client removes its tools: client2's ephemeral_tool removed
  after disconnect, client1's stable_tool persists
- Update existing test: client2 resumes with NO tools (doesn't overwrite)
- Regenerated snapshots for existing tests (minor wording changes)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Bump SDK_PROTOCOL_VERSION from 2 to 3 to match runtime breaking change
- Wrap fallback RPC calls in _executeToolAndRespond and
  _executePermissionAndRespond with try/catch to prevent unhandled
  rejections if the connection is disposed mid-flight

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
These are no different from any other event handler — let rejections
propagate naturally.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@SteveSandersonMS SteveSandersonMS requested a review from a team as a code owner March 5, 2026 13:48
Copilot AI review requested due to automatic review settings March 5, 2026 13:48
@github-actions
Copy link
Contributor

github-actions bot commented Mar 5, 2026

Cross-SDK Consistency Review

This PR introduces a breaking architectural change to the Node.js SDK by migrating from an RPC callback model to a broadcast event model for handling tool calls and permission requests. This change bumps the protocol version from 2 to 3.

🚨 Critical Inconsistency: Protocol Version Mismatch

Node.js SDK (this PR):

  • Protocol version: 3
  • Architecture: Broadcast event model (external_tool.requested / permission.requested events)
  • Clients respond via: session.tools.handlePendingToolCall / session.permissions.handlePendingPermissionRequest RPCs

Python, Go, and .NET SDKs (current state):

  • Protocol version: 2
  • Architecture: RPC callback model (tool.call / permission.request RPC requests)
  • Clients respond synchronously to the RPC request

Impact

This creates a breaking inconsistency across the SDK implementations:

  1. Protocol incompatibility: The Node.js SDK (v3) will not be compatible with the same runtime version as Python/Go/.NET SDKs (v2)
  2. Feature disparity: Only the Node.js SDK will support multi-client scenarios where multiple clients can observe and respond to tool/permission requests
  3. API breaking changes: The internal implementation has changed significantly (removal of handleToolCallRequest, handlePermissionRequest methods in client, addition of _handleBroadcastEvent in session)

Recommendation

To maintain cross-SDK consistency, all four SDKs should be updated together to implement the broadcast event model and bump to protocol version 3:

Required changes for Python SDK:

  • Update python/copilot/sdk_protocol_version.py: SDK_PROTOCOL_VERSION = 3
  • Remove RPC handlers: self._client.set_request_handler("tool.call", ...) and self._client.set_request_handler("permission.request", ...)
  • Add event handlers: Listen for external_tool.requested and permission.requested session events
  • Implement response methods: Add session.tools.handle_pending_tool_call() and session.permissions.handle_pending_permission_request() RPC calls

Required changes for Go SDK:

  • Update go/sdk_protocol_version.go: SdkProtocolVersion = 3
  • Remove RPC handlers: c.client.SetRequestHandler("tool.call", ...) and c.client.SetRequestHandler("permission.request", ...)
  • Add event handlers: Listen for external_tool.requested and permission.requested session events
  • Implement response methods: Add session.tools.HandlePendingToolCall() and session.permissions.HandlePendingPermissionRequest() RPC calls

Required changes for .NET SDK:

  • Update dotnet/src/SdkProtocolVersion.cs: private const int Version = 3;
  • Remove RPC handlers: rpc.AddLocalRpcMethod("tool.call", ...) and rpc.AddLocalRpcMethod("permission.request", ...)
  • Add event handlers: Listen for external_tool.requested and permission.requested session events
  • Implement response methods: Add session.Tools.HandlePendingToolCall() and session.Permissions.HandlePendingPermissionRequest() RPC calls

Alternative Approach

If updating all SDKs simultaneously is not feasible, consider:

  1. Keeping this PR in draft until all SDKs are ready
  2. Coordinating a synchronized release across all languages
  3. Documenting the temporary inconsistency and migration plan

Note

The PR description mentions: "⚠️ E2E tests will fail until the corresponding runtime update is released" — this confirms that this is a coordinated breaking change with the runtime. All SDK implementations should be updated together to maintain feature parity and avoid confusion for users who might want to use different language SDKs with the same runtime version.

Generated by SDK Consistency Review Agent for issue #686 ·

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the Node SDK to handle external tool calls and permission requests via broadcast session events (instead of per-connection RPC callbacks), aligns the Node client with a breaking runtime protocol change, and adds multi-client E2E coverage for the new behavior.

Changes:

  • Switch tool/permission handling to the broadcast event model by intercepting external_tool.requested and permission.requested in CopilotSession and responding via new session RPC methods.
  • Bump Node SDK_PROTOCOL_VERSION to 3 and regenerate protocol typings (rpc.ts, session-events.ts) to match updated runtime schemas.
  • Update/add E2E tests and snapshots, including new multi-client broadcast scenarios.

Reviewed changes

Copilot reviewed 16 out of 18 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
nodejs/src/client.ts Removes legacy tool.call / permission.request request handlers and relies on session event dispatch for tool/permission handling.
nodejs/src/session.ts Adds internal handling for broadcast request events and responds via new session RPC methods.
nodejs/src/sdkProtocolVersion.ts Bumps Node SDK protocol version from 2 → 3.
nodejs/src/generated/rpc.ts Adds new session RPC methods for responding to pending tool calls and permission requests; updates some schema fields.
nodejs/src/generated/session-events.ts Regenerates/expands session event types, including new external tool events and updated permission completion payload.
nodejs/test/e2e/harness/sdkTestContext.ts Adds useStdio option to allow TCP-mode tests needed for multi-client scenarios.
nodejs/test/e2e/multi-client.test.ts Adds new multi-client E2E tests validating broadcast semantics for tools and permissions.
test/snapshots/tools/*.yaml Updates tool-related snapshots to reflect new runtime/SDK behavior and outputs.
test/snapshots/multi_client/*.yaml Adds new snapshots for multi-client broadcast scenarios.
Comments suppressed due to low confidence (1)

nodejs/src/client.ts:1281

  • tool.call request handling (and the private handleToolCallRequest helper) was removed here, but there is still a unit test referencing handleToolCallRequest (see nodejs/test/client.test.ts). This will fail TypeScript compilation/tests until that test is updated or removed to reflect the new broadcast-event tool handling model.
        this.connection.onNotification("session.event", (notification: unknown) => {
            this.handleSessionEventNotification(notification);
        });

        this.connection.onNotification("session.lifecycle", (notification: unknown) => {
            this.handleSessionLifecycleNotification(notification);
        });

        // External tool calls and permission requests are now handled via broadcast events:
        // the server sends external_tool.requested / permission.requested as session event
        // notifications, and CopilotSession._dispatchEvent handles them internally by
        // executing the handler and responding via session.tools.respond / session.permissions.respond RPC.

SteveSandersonMS and others added 2 commits March 5, 2026 18:09
- Remove unused session2 variable in multi-client test
- Document fire-and-forget semantics in _handleBroadcastEvent
- Fix null/undefined handling in tool result serialization
- Fix stale RPC method names in comment

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@SteveSandersonMS SteveSandersonMS marked this pull request as draft March 5, 2026 18:19
@github-actions
Copy link
Contributor

github-actions bot commented Mar 5, 2026

⚠️ Cross-SDK Consistency Review

This PR introduces a breaking protocol change (version 2 → 3) and implements a new broadcast event model for tool and permission handling in the Node.js SDK only. This creates a significant consistency gap with the other SDK implementations.

Current State After This PR

SDK Protocol Version Tool/Permission Pattern Multi-Client Support
Node.js 3 Broadcast events Yes
Python 2 ❌ RPC callbacks ❌ No ❌
Go 2 ❌ RPC callbacks ❌ No ❌
.NET 2 ❌ RPC callbacks ❌ No ❌

Key Inconsistencies

1. Protocol Version Mismatch

The Node SDK bumps to protocol version 3, while Python, Go, and .NET remain on version 2. This means:

  • Node SDK clients will only work with runtime version 3+
  • Other SDKs will only work with runtime version 2
  • Users cannot mix SDK languages in multi-client scenarios

Files affected:

  • nodejs/src/sdkProtocolVersion.ts - bumped to 3
  • python/copilot/sdk_protocol_version.py - still 2
  • go/sdk_protocol_version.go - still 2
  • dotnet/src/SdkProtocolVersion.cs - still 2

2. Tool & Permission Handling Pattern

Node SDK now uses broadcast events (external_tool.requested, permission.requested) and responds via new RPC methods, while other SDKs still use the old callback pattern:

Node.js (new pattern):

  • Listen for external_tool.requested / permission.requested session events
  • Respond via session.tools.handlePendingToolCall / session.permissions.handlePendingPermissionRequest RPC

Python/Go/.NET (old pattern):

  • Still register tool.call / permission.request RPC request handlers
  • Respond directly to the RPC request

Files that need updates:

  • python/copilot/client.py:1351-1352 - still sets tool.call and permission.request handlers
  • go/client.go:1289-1290 - still sets tool.call and permission.request handlers
  • dotnet/src/Client.cs:1125-1126 - still sets tool.call and permission.request handlers

3. Multi-Client Support

The Node SDK now supports multiple clients connecting to the same session (all receive broadcast events). This capability doesn't exist in other SDKs and would require:

  • Event handling for broadcast request events
  • Coordination of responses from multiple clients
  • E2E tests for multi-client scenarios

Files that demonstrate this:

  • nodejs/test/e2e/multi-client.test.ts - 5 new multi-client test cases
  • ❌ Python, Go, .NET - no multi-client tests or support

4. New Extension Export

Node SDK adds extension.ts export for child process scenarios - unclear if this is needed in other SDKs.

Recommendations

To maintain cross-SDK consistency, the following options should be considered:

Option A: Update all SDKs in parallel

  • Port the broadcast event model to Python, Go, and .NET
  • Bump all SDKs to protocol version 3 simultaneously
  • Add multi-client support and tests across all languages
  • Ensures feature parity but requires significant coordination

Option B: Staged rollout with version compatibility

  • Document that Node SDK v3 requires runtime v3+
  • Plan follow-up PRs to port changes to other SDKs
  • Track the migration in a GitHub issue
  • Temporary inconsistency is acceptable if tracked and planned

Option C: Keep protocol version 2 support in Node SDK

  • Make the Node SDK backward compatible with both v2 and v3 protocols
  • Allow gradual migration of other SDKs
  • More complex but maintains broader compatibility

Questions for Maintainers

  1. Is there a timeline for porting these changes to Python, Go, and .NET?
  2. Should protocol version 3 support be tracked as a feature gap in other SDKs?
  3. Are there any runtime compatibility considerations that affect the rollout strategy?
  4. Should this PR be labeled as breaking change / requires coordination across SDKs?

Note: This is a technical consistency review, not a judgment on code quality. The Node SDK implementation looks well-designed - the concern is ensuring the other SDKs evolve in parallel to maintain API parity across languages.

Generated by SDK Consistency Review Agent for issue #686 ·

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generated by SDK Consistency Review Agent for issue #686

* This must match the version expected by the copilot-agent-runtime server.
*/
export const SDK_PROTOCOL_VERSION = 2;
export const SDK_PROTOCOL_VERSION = 3;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-SDK Consistency: This protocol version bump to 3 is not reflected in the other SDKs:

  • Python: python/copilot/sdk_protocol_version.py - still at version 2
  • Go: go/sdk_protocol_version.go - still at version 2
  • .NET: dotnet/src/SdkProtocolVersion.cs - still at version 2

This creates a compatibility issue where Node SDK clients will only work with runtime v3+, while other SDK clients remain on v2. Consider coordinating this version bump across all SDKs or documenting the migration plan.

@@ -0,0 +1,289 @@
/*---------------------------------------------------------------------------------------------
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-SDK Consistency: These multi-client E2E tests demonstrate new functionality that doesn't exist in the other SDKs. For feature parity, similar tests should be added to:

  • python/e2e/ - no multi-client tests currently
  • go/ - no multi-client tests currently
  • dotnet/test/ - no multi-client tests currently

The multi-client capability requires the broadcast event model, so these tests can't be ported until the other SDKs implement the same pattern.

* rejections, consistent with standard EventEmitter / event handler semantics.
* @internal
*/
private _handleBroadcastEvent(event: SessionEvent): void {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-SDK Consistency: This new broadcast event handling pattern for external_tool.requested and permission.requested is Node-only. The other SDKs still use the old RPC callback pattern:

  • Python: client.py:1351-1352 - still registers tool.call and permission.request RPC handlers
  • Go: client.go:1289-1290 - same old pattern
  • .NET: Client.cs:1125-1126 - same old pattern

For consistency, these SDKs would need similar _handleBroadcastEvent implementations that:

  1. Listen for broadcast session events instead of RPC requests
  2. Execute handlers locally
  3. Respond via the new RPC methods (session.tools.handlePendingToolCall, session.permissions.handlePendingPermissionRequest)


import { CopilotClient } from "./client.js";

export const extension = new CopilotClient({ isChildProcess: true });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-SDK Consistency: This new extension export for child process scenarios is Node-only.

Questions:

  1. Is this functionality needed in other SDKs?
  2. Should Python, Go, and .NET provide equivalent child process integration patterns?
  3. Is this a Node-specific use case or a general SDK feature?

If this is generally useful, consider adding equivalent exports in the other SDKs.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 5, 2026

Cross-SDK Consistency Review

This PR introduces a significant architectural change to the Node.js SDK, replacing the RPC callback pattern for tool calls and permission requests with a broadcast event model. This is a breaking change (protocol version bumped from 2 → 3) that enables multi-client scenarios where multiple SDK clients can connect to the same CLI session.

Summary of Changes (Node.js SDK)

Before (Protocol v2): The runtime invoked tool.call and permission.request as RPC requests directly to a single client, which responded synchronously.

After (Protocol v3): The runtime broadcasts external_tool.requested and permission.requested as session events to all connected clients. Each client with a matching handler executes it locally and responds via new RPC methods:

  • session.tools.handlePendingToolCall
  • session.permissions.handlePendingPermissionRequest

This enables:

  • Multi-client tool registration (union semantics - each client can register different tools)
  • Multi-client permission handling (first response wins)
  • Broadcast event visibility (all clients see tool requests and completions)

⚠️ Cross-SDK Consistency Gap

The Python, Go, and .NET SDKs still use the old protocol v2 pattern:

Python (python/copilot/client.py):

# Line 1351-1352
self._client.set_request_handler("tool.call", self._handle_tool_call_request)
self._client.set_request_handler("permission.request", self._handle_permission_request)

Go (go/client.go):

// Line 1289-1290
c.client.SetRequestHandler("tool.call", jsonrpc2.RequestHandlerFor(c.handleToolCallRequest))
c.client.SetRequestHandler("permission.request", jsonrpc2.RequestHandlerFor(c.handlePermissionRequest))

.NET (dotnet/src/Client.cs):

// Line 1125-1126
rpc.AddLocalRpcMethod("tool.call", handler.OnToolCall);
rpc.AddLocalRpcMethod("permission.request", handler.OnPermissionRequest);

All three SDKs also still have SDK_PROTOCOL_VERSION = 2.

Recommendations

To maintain cross-SDK consistency and enable the same multi-client capabilities across all languages:

  1. Update Python, Go, and .NET to implement the same broadcast event model:

    • Remove tool.call and permission.request RPC request handlers
    • Listen for external_tool.requested and permission.requested session events
    • Respond via session.tools.handlePendingToolCall and session.permissions.handlePendingPermissionRequest RPC calls
    • Bump SDK_PROTOCOL_VERSION to 3 in all SDKs
  2. Add equivalent E2E tests for multi-client scenarios in each SDK (similar to nodejs/test/e2e/multi-client.test.ts)

  3. Update SDK documentation to explain:

    • The broadcast model and multi-client capabilities
    • Breaking changes from protocol v2 → v3
    • Migration guide for existing applications

Timeline Considerations

The PR description notes that "E2E tests will fail until the corresponding runtime update is released". This suggests:

  • The Node.js SDK changes can merge once the runtime is updated
  • Python, Go, and .NET should be updated in lockstep to avoid a prolonged period where only Node.js supports protocol v3

Alternative: Phased Rollout

If immediate parity isn't feasible, consider:

  • Documenting this as a Node.js-only feature (temporarily)
  • Creating tracking issues for Python (#XXX), Go (#XXX), and .NET (#XXX) implementations
  • Adding a section to the main README noting feature parity status

Verdict: This is an excellent architectural improvement that enables powerful multi-client scenarios. However, it creates a significant consistency gap across SDKs. I recommend planning the rollout to other languages before or shortly after merging this PR to maintain the SDK's cross-language feature parity.

Generated by SDK Consistency Review Agent for issue #686 ·

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants